package org.nvframe.manager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.ContactListener;
import org.jbox2d.dynamics.Steppable;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.contacts.ContactPoint;
import org.jbox2d.dynamics.contacts.ContactResult;

import org.nvframe.component.physics.state.PhysicsPosition;
import org.nvframe.entity.Entity;
import org.nvframe.event.EventService;
import org.nvframe.event.eventtype.CollisionEvent;
/**
 * 
 * @author Nik Van Looy
 */
public class CollisionManager implements ContactListener, Steppable {

	private static CollisionManager _instance = new CollisionManager();
	
	public static CollisionManager getInstance() {
		return _instance;
	}
	
	private World world;
	private List<Body> deletionBuffer = new ArrayList<Body>();
	private Map<Entity, ArrayList<CollisionEvent>> collisionEventBuffer = new HashMap<Entity, ArrayList<CollisionEvent>> ();

	private CollisionManager() {
	}
	
	public void registerForWorld(World world) {
		this.world = world;
		this.world.registerPostStep(this);
	}

	@Override
	public void add(ContactPoint point) {
		if (point.shape1.getBody().getUserData() == null || point.shape2.getBody().getUserData() == null)
			return;
		
		Entity entity1 = (Entity) point.shape1.getBody().getUserData();
		Entity entity2 = (Entity) point.shape2.getBody().getUserData();
		
		if(entity1 == null || entity2 == null)
			return;

		// fire the event to both entities
		CollisionEvent event = new CollisionEvent(entity1, entity2);
		
		//EventService.getInstance().fireEvent(event, entity1);
		//EventService.getInstance().fireEvent(event, entity2);
		sheduleCollisionEvent(entity1, event);
		sheduleCollisionEvent(entity2, event);
		
		// removal when bullet
		Body body1 = point.shape1.getBody();
		Body body2 = point.shape2.getBody();
		
		if(body1.isBullet() && body2.isBullet())
			return;
		
		/*if(body1.isBullet())
			entity1.removed();
		
		if(body2.isBullet())
			entity2.removed();*/
	}
	
	/**
	 * check if two given entities are colliding
	 * 
	 * @param source 
	 * @param target
	 * @return true if a given entity is colliding with the owner
	 */
	public boolean isCollidingWith(Entity source, Entity target) {
		if(source == null || target == null)
			return false;
		
		PhysicsPosition position = (PhysicsPosition) source.getComponent(PhysicsPosition.class);
		Body body = position.getBody();
		
		if(body == null) 
			return false;
		
		PhysicsPosition targetPosition = (PhysicsPosition) target.getComponent(PhysicsPosition.class);
		
		if(targetPosition == null)
			return false;
		
		Body targetBody = targetPosition.getBody();
		
		Set<Body> targets = body.getBodiesInContact();
		
		if(targets.contains(targetBody))
			return true;
		
		return false;
	}
	
	public void sheduleCollisionEvent(Entity entity, CollisionEvent event) {
		if(!collisionEventBuffer.containsKey(entity))
			collisionEventBuffer.put(entity, new ArrayList<CollisionEvent> ());
		collisionEventBuffer.get(entity).add(event);
	}
	
	private void executeCollisionEvents() {
		for(Entity entity : collisionEventBuffer.keySet()) {
			for(CollisionEvent event : collisionEventBuffer.get(entity))
				EventService.getInstance().fireEvent(event, entity);
		}
		
		collisionEventBuffer.clear();
	}
	
	public void sheduleBodyDelition(Entity entity) {
		Body body = ((PhysicsPosition) entity.getComponent(PhysicsPosition.class)).getBody();
		deletionBuffer.add(body);
	}
	
	public void executeBodyDeletion() {
		if(deletionBuffer.size() == 0)
			return;
		
		// create a HashSet to remove duplicates
		HashSet<Body> hashSet = new HashSet<Body>(deletionBuffer);
		ArrayList<Body> deletionBufferCpy = new ArrayList<Body>(hashSet) ;
		  
		for(Body body : deletionBufferCpy) {
			body.putToSleep();

			if(ConfigManager.getInstance().getActive("debugMode")) {
				Entity entity = (Entity) body.getUserData();
				System.out.println("removing physics body from entity [id: " + entity.getId() +
						", instance: " + entity.getInstanceId() + "]");
			}
			
			body.setUserData(null);
			world.destroyBody(body);
		}
		
		deletionBuffer.clear();
	}
	
	@Override
	public void step(float dt, int iterations) {
		executeCollisionEvents();
		executeBodyDeletion();
	}

	@Override
	public void persist(ContactPoint point) {}

	@Override
	public void remove(ContactPoint point) {}

	@Override
	public void result(ContactResult point) {}
}
